package jee.boot.common.basic;

import com.github.pagehelper.PageHelper;
import jee.boot.common.basic.result.MapData;
import jee.boot.entity.system.Filter;
import jee.boot.common.properties.ProjectConfig;
import jee.boot.common.remote.RemoteEndpoint;
import jee.boot.common.standard.DocumentStorageClient;
import jee.boot.common.utils.StringUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Service基类
 *
 * @param <D>
 * @param <T>
 * @author zhilei.wang
 */
public abstract class CrudService<D extends CrudDao<T>, T extends DataEntity<T>> extends BaseService {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    protected BaseConfig baseConfig;
    @Autowired
    protected ProjectConfig projectConfig;
    @Autowired(required = false)
    protected DocumentStorageClient documentStorageClient;
    @Autowired
    protected RemoteEndpoint remoteEndpoint;
    /**
     * 持久层对象
     */
    @Autowired
    protected D dao;

    /**
     * 获取单条数据
     *
     * @param entity
     * @return
     */
    public T get(T entity) {
        entity.preQuery();
        T t = dao.get(entity);
        entity.postQuery(t);
        return t;
    }

    /**
     * 查询列表数据
     *
     * @param entity
     * @return
     */
    public List<T> findList(T entity) {
        entity.preQuery();
        List<T> list = dao.findList(entity);
        entity.postUpdate(list);
        return list;
    }

    /**
     * 查询分页数据
     *
     * @param entity
     * @return
     */
    public Page<T> findPage(T entity) {
        Page<T> page = entity.getPage();
        if (page == null) {
            page = new Page<T>(1, 10);
            entity.setPage(page);
        }
        if (page.getPageSize() < 0) {
            page.setPageSize(0);
        }
        entity.preQuery();
        com.github.pagehelper.Page<T> startPage = PageHelper.startPage(page.getPageNo(), page.getPageSize(), true, true, true);
        page.setList(dao.findList(entity));
        page.setCount(startPage.getTotal());
        page.setPageNo(startPage.getPageNum());
        page.setPageSize(startPage.getPageSize());
        return page;
    }

    /**
     * 插入数据
     *
     * @param entity
     */
    public void save(T entity) {
        entity.preInsert();
        int flag = dao.insert(entity);
        entity.postInsert(flag);
    }

    /**
     * 更新数据
     *
     * @param entity
     */
    public void update(T entity) {
        entity.preUpdate();
        int flag = dao.update(entity);
        entity.postUpdate(flag);
    }

    /**
     * 动态更新,只对有值属性进行update操作,控制的字段将被忽略更新
     *
     * @param entity
     */
    public void dynamicUpdate(T entity) {
        entity.preUpdate();
        int flag = dao.dynamicUpdate(entity);
        entity.postUpdate(flag);
    }

    /**
     * 删除数据
     *
     * @param entity
     */
    public void delete(T entity) {
        dao.delete(entity);
    }

    /**
     * 删除数据-一对多情况下,更新子表数据时候使用
     *
     * @param entity
     */
    public void deleteByUpdateFlag(T entity) {
        dao.deleteByUpdateFlag(entity);
    }

    /**
     * 处理一对多的子表的查看操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemEntity 子数据对象,用于查询使用,new 一个空对象即可
     * @param <T>        对返回类型进行强转
     * @return 返回的子表的数据列表
     */
    public <T> T getOneToMany(CrudDao itemDao, DataEntity entity, DataEntity itemEntity) {
        return (T) getOneToMany(itemDao, entity, itemEntity, "head_id");
    }

    /**
     * 处理一对多的子表的查看操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemEntity 子数据对象,用于查询使用,new 一个空对象即可
     * @param foreignKey 外键key,默认是head_id
     * @param <T>        对返回类型进行强转
     * @return 返回的子表的数据列表
     */
    public <T> T getOneToMany(CrudDao itemDao, DataEntity entity, DataEntity itemEntity, String foreignKey) {
        Map<String, Field> fieldAsMap = itemEntity.getFieldAsMap(itemEntity.getClass());
        String columnByForeignKey = itemEntity.getColumnByForeignKey(fieldAsMap, foreignKey);
        itemEntity.addFilter(new Filter(columnByForeignKey, "=", entity.getId()));
        return (T) itemDao.findList(itemEntity);
    }

    /**
     * 处理一对多的子表的新增操作
     *
     * @param itemDao  子表的Dao,使用该dao对子表数据进行操作
     * @param entity   主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemList 需要操作的子表数据,通常在主数据对象中获取
     */
    public void saveOneToMany(CrudDao itemDao, DataEntity entity, List<? extends DataEntity> itemList) {
        saveOneToMany(itemDao, entity, "head_id", itemList);
    }

    /**
     * 处理一对多的子表的新增操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param foreignKey 外键key,默认是head_id
     * @param itemList   需要操作的子表数据,通常在主数据对象中获取
     */
    public void saveOneToMany(CrudDao itemDao, DataEntity entity, String foreignKey, List<? extends DataEntity> itemList) {
        for (DataEntity item : itemList) {
            item.setForeignKey(foreignKey);
            item.setForeignValue(entity.getId());
            item.preInsert();
            itemDao.insert(item);
        }
    }

    /**
     * 处理一对多的子表的修改操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemEntity 子数据对象,用于查询使用,new 一个空对象即可
     * @param itemList   需要操作的子表数据,通常在主数据对象中获取
     */
    public void updateOneToMany(CrudDao itemDao, DataEntity entity, DataEntity itemEntity, List<? extends DataEntity> itemList) {
        updateOneToMany(itemDao, entity, itemEntity, "head_id", itemList);
    }

    /**
     * 处理一对多的子表的修改操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemEntity 子数据对象,用于查询使用,new 一个空对象即可
     * @param foreignKey 外键key,默认是head_id
     * @param itemList   需要操作的子表数据,通常在主数据对象中获取
     */
    public void updateOneToMany(CrudDao itemDao, DataEntity entity, DataEntity itemEntity, String foreignKey, List<? extends DataEntity> itemList) {
        String updateFlag = String.valueOf(System.currentTimeMillis());
        for (DataEntity item : itemList) {
            item.setForeignKey(foreignKey);
            item.setForeignValue(entity.getId());
            item.setUpdateFlag(updateFlag);
            if (StringUtils.isBlank(item.getId())) {
                item.preInsert();
                itemDao.insert(item);
            } else {
                item.preUpdate();
                itemDao.update(item);
            }
        }
        itemEntity.setForeignKey(foreignKey);
        itemEntity.setForeignValue(entity.getId());
        itemEntity.setUpdateFlag(updateFlag);
        itemDao.deleteByUpdateFlag(itemEntity);
    }


    /**
     * 处理一对多的子表的删除操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemEntity 子数据对象,用于查询使用,new 一个空对象即可
     */
    public void deleteOneToMany(CrudDao itemDao, DataEntity entity, DataEntity itemEntity) {
        deleteOneToMany(itemDao, entity, itemEntity, "head_id");
    }

    /**
     * 处理一对多的子表的删除操作
     *
     * @param itemDao    子表的Dao,使用该dao对子表数据进行操作
     * @param entity     主数据对象,通常是修改主表的数据对象,通过该对象获取id,即子表的外键值
     * @param itemEntity 子数据对象,用于查询使用,new 一个空对象即可
     * @param foreignKey 外键key,默认是head_id
     */
    public void deleteOneToMany(CrudDao itemDao, DataEntity entity, DataEntity itemEntity, String foreignKey) {
        itemEntity.setForeignKey(foreignKey);
        itemEntity.setForeignValue(entity.getId());
        itemDao.deleteByForeignKey(itemEntity);
    }

    /**
     * 导入
     *
     * @param file
     * @return
     */
    public Object importData(MultipartFile file) {
        //需要被子类覆盖实现
        return null;
    }

    /**
     * 导出
     */
    public void exportData(T entity, HttpServletResponse response) {
        //需要被子类覆盖实现
    }

    /**
     * 导出模板
     */
    public void importTemplate(T entity, HttpServletResponse response) {
        //需要被子类覆盖实现
    }

    /**
     * 批量新增
     *
     * @param list
     */
    public void batchSave(List<T> list) {
        if (list != null && !list.isEmpty()) {
            for (T entity : list) {
                entity.preInsert();
            }
            dao.batchInsert(list);
        }
    }

    /**
     * 批量修改,批量修改动作不进行任何的逻辑校验,如果需要进行逻辑校验,请覆写当前方法
     *
     * @param list
     */
    public void batchUpdate(List<T> list) {
        if (list != null && !list.isEmpty()) {
            List<T> tempInsertList = new ArrayList<>();
            List<T> tempDeleteList = new ArrayList<>();
            for (T entity : list) {
                // 默认根据entity的id识别是插入还是修改
                if (StringUtils.isBlank(entity.getId())) {
                    entity.preInsert();
                    tempInsertList.add(entity);
                } else {
                    if (StringUtils.equals(DataEntity.DATA_STATUS_DELETED, entity.getStatus())) {
                        tempDeleteList.add(entity);
                    } else {
                        entity.preUpdate();
                        dao.update(entity);
                    }
                }
            }
            if (!tempInsertList.isEmpty()) {
                dao.batchInsert(tempInsertList);
            }
            if (!tempDeleteList.isEmpty()) {
                T entity = list.get(0);
                entity.setIdList(tempDeleteList.stream().map(d -> d.getId()).collect(Collectors.toList()));
                dao.batchDelete(entity);
            }
        }
    }

    /**
     * 批量删除
     *
     * @param entity
     */
    public void batchDelete(T entity) {
        if (entity.getIdList() != null && !entity.getIdList().isEmpty()) {
            dao.batchDelete(entity);
        }
    }

    /**
     * 逻辑删除
     *
     * @param entity
     */
    public void logicDelete(T entity) {
        dao.logicDelete(entity);
    }

    /**
     * 存储文件
     *
     * @param file 上传的文件进行存储
     * @return 文件ID, 可以根据这个文件id下载附件
     */
    public <T> T storeFile(MultipartFile file) {
        if (file == null) {
            throw new ServiceException(1305);
        }
        try {
            return documentStorageClient.uploadFromMultipartFile(file);
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            throw new ServiceException(1306, MapData.instance().put("message",e.getMessage()).data());
        }
    }

    /**
     * 数据唯一性校验
     * @param entity 实体数据
     * @param fields 需要校验的字段
     */
    public void uniquenessCheck(T entity,String ... fields){
        if(entity!=null && fields!=null && fields.length >0){
            // 如果是多个字段联合重复,需要拼接多个条件
            for(String f:fields) {
                try {
                    Object property = PropertyUtils.getProperty(entity, f);
                    entity.addFilter(new Filter(f, "=", property));
                }catch (Exception e){
                    logger.error(e.getMessage(),e);
                    throw new ServiceException(e);
                }
            }
            // 如果是修改操作,则要拼接上id属性
            if(StringUtils.isNotBlank(entity.getId())) {
                entity.addFilter(new Filter("id", "<>", entity.getId()));
            }
            List<T> list = findList(entity);
            if(list!=null && !list.isEmpty()){
                StringBuilder sb=new StringBuilder();
                for(String f:fields) {
                    try {
                        Object property = PropertyUtils.getProperty(entity, f);
                        sb.append(f).append("[").append(property).append("], ");
                    }catch (Exception e){
                        logger.error(e.getMessage(),e);
                        throw new ServiceException(e);
                    }
                }
                throw new ServiceException(1312,MapData.instance().put("message",sb.toString()).data());
            }
        }
    }
}
